/**
 * 
 */
package com.gxa.hualian.util;

	import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

/**
 * 分析: 数据库操作封装的思路
 * 1. 1-4 是固定的 / 8 是固定的 操作
 * 2. 每次执行操作都通过内部的创建连接方法(封装)
 * 3. 把创建预编译命令对象单独封装(传参->用可变参数升级)
 * 4. DML->execUpdate   DQL->execQuery
 * @author bilei
 * @date 2022年4月14日 
 */
public class DBUtil {
	
	private Connection connection = null;
	private ResultSet resultSet = null;
	private PreparedStatement preparedStatement = null;
	
	// 改造2: 将driver/url/username/password通过配置属性文件的方式
	// 增加一个环节: 读取(I/O)属性配置文件

	Properties properties = null;
	// 在语句块中获取数据库连接的属性
	{
		try {
			// 声明文件流,读取属性配置文件
			// 服务器tomcat的绝对路径,才能被File找到(ServletContext): 太繁琐
			//ClassLoader 可以加载方法区(classes)中的类信息

			//1. 先获取Class对象
			// 通过任意一个Class对象都可以获取到ClassLoader对象
			Class<DBUtil> dbUtilClass = DBUtil.class;

			//2. 获取一个类加载器ClassLoader对象
			//加载器ClassLoader对象的作用: 加载class文件,表示可以访问class所在的目录
			ClassLoader classLoader = dbUtilClass.getClassLoader();

			//3. ClassLoader对象到classes下访问指定的文件资源,并返回流
			InputStream resourceAsStream = classLoader.getResourceAsStream("db.properties");

			// 服务器中不能用相对路径
			//InputStream inputStream = new FileInputStream("db.properties");

			// 声明Properties
			properties = new Properties();
			properties.load(resourceAsStream);
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 该连接方法就是为createPreparedStatement准备的,所以声明为private
	 * @return
	 * @throws Exception
	 */
	private Connection createConnection() throws Exception {

		// 判断connection对象的状态
		//如果connection是null 或者 已经关闭了(虽然关闭了,但是非null)
		if(connection == null || connection.isClosed()) {
			Class.forName(properties.getProperty("driver"));
			String url = properties.getProperty("url");  
			String username = properties.getProperty("username");
			String password = properties.getProperty("password");

			/*Class.forName("com.mysql.jdbc.Driver");
			String url = "jdbc:mysql://localhost:3306/bj291?useSSL=false&serverTimezone=UTC&characterEncoding=UTF-8";
			String username = "root";
			String password = "root";*/
			connection = DriverManager.getConnection(url, username, password);

		}
		
		// 如果不满足条件,也就直接返回connection对象
		return connection;
	}
	
	
	/**
	 * 改造1: 生成预编译命令对象的同时,可否把参数一并传递 
	*  问题: 如何在参数个数不确定的情况下,还能封装成一个统一的方法
	*  传统做法: 传递Object[], 把参数个数的不确定性留到传实参的时候
	*  选择使用可变参数来更灵活的实现
	 * @param sql 预编译的sql语句
	 * @param params 可变参数(数组)
	 * @return 传递了参数的预编译对象
	 * @throws Exception
	 */
	public PreparedStatement createPreparedStatement(String sql,Object...params) throws Exception {
		// 生成预编译对象
		preparedStatement = createConnection().prepareStatement(sql);
		// 遍历可变参数(数组),i表示数组下标
		for (int i = 0; i < params.length; i++) {
			// setObject(paramIndex,value)  paramIndex表示?在sql中的顺序,从1开始
			preparedStatement.setObject(i+1, params[i]);
		}
		return preparedStatement;
	}
	
	/**
	 * 执行增删改操作的方法
	 * @param preparedStatement 已经预编译了增删改sql,且已经传递了参数的对象
	 * @return 影响的行数
	 * @throws Exception 
	 */
	public int execUpdate(PreparedStatement preparedStatement) throws Exception {
		return preparedStatement.executeUpdate();
	}
	
	/**
	 * @param preparedStatement 已经预编译了查询sql,且已经传递了参数的对象
	 * @return
	 * @throws Exception
	 */
	public ResultSet execQuery(PreparedStatement preparedStatement) throws Exception {
		return preparedStatement.executeQuery();
	}
	
	
	
	public void closeResultSet() {
		if(resultSet!=null) {
			try {
				resultSet.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
	
	public void closePreparedStatement() {
		if(preparedStatement!=null) {
			try {
				preparedStatement.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
	
	public void closeConnection() {
		if(connection!=null) {
			try {
				connection.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
	
	public void closeAll() {
		closeResultSet();
		closePreparedStatement();
		closeConnection();
	}
}
